5.4 并发与同步:锁、条件变量与无锁编程

Go语言的并发特性是其一大亮点,通过Goroutine实现并发编程,可以大幅提高程序的执行效率。

在并发编程中,如何确保多个Goroutine安全地访问共享资源是一个重要的问题。

本节将介绍几种常用的同步机制,包括锁、条件变量,以及无锁编程。

本节代码存放目录为 lesson16

互斥锁

互斥锁(sync.Mutex)是一种用于保护共享资源的机制,确保同一时间只有一个 Goroutine能够访问特定的代码块或资源。

通过锁住共享资源,可以防止数据竞争和不一致的问题。简单的来说就是将数据增加一个标识,使用的时候标识更新为1,那么其他的协程就不可以使用。

以下是一个使用互斥锁的简单示例:

var (
    mu      sync.Mutex
    counter int
)

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

在上面的代码中,increment函数使用了mu.Lock()mu.Unlock()来确保counter++操作是并发安全的。

尽管互斥锁能够有效保护共享资源,但在高并发场景下,锁的争用可能会导致性能瓶颈,甚至引发死锁。

条件变量

条件变量(sync.Cond)允许Goroutine等待某个条件的满足,并在条件满足后继续执行。它常与互斥锁配合使用,用于实现更复杂的同步逻辑。

以下是一个使用条件变量的示例:

var (
    muC   sync.Mutex
    cond  = sync.NewCond(&muC)
    ready = false
    wg    sync.WaitGroup
)

func waitCondition() {
    defer wg.Done()
    cond.L.Lock()
    for !ready {
        cond.Wait()
    }
    // 执行其他操作
    fmt.Println("Cond unlock")
    cond.L.Unlock()
}

func signalCondition() {
    defer wg.Done()
    fmt.Println("signalCondition")
    cond.L.Lock()
    ready = true
    cond.Signal() // 唤醒一个等待的 Goroutine
    cond.L.Unlock()
}

wg.Add(2)
go waitCondition()

time.Sleep(time.Duration(2) * time.Second)

go signalCondition()

wg.Wait()

结果输出如下所示:

signalCondition
Cond unlock

在上面的代码中,waitCondition函数等待ready变量变为 true

一旦signalCondition函数将ready设置为true并调用cond.Signal(),等待的Goroutine就会被唤醒。

这种方式在一些流程控制上还是比较好用的,比如达到某些条件执行下一步存储日志等。

无锁编程

无锁编程通过使用原子操作来避免锁的开销,从而提高并发性能。Go 语言提供了sync/atomic包,支持无锁的原子操作。

以下是一个使用原子操作的简单示例:

var (
    counterAuto int64
)

func incrementAuto() {
    atomic.AddInt64(&counterAuto, 1)
}

在这个例子中,atomic.AddInt64提供了一种无锁的方式来安全地增加counter的值。

无锁编程能够显著提高性能,但它的实现通常比使用锁更加复杂,并且可能难以调试。

因此,只有在性能瓶颈明显且锁的开销较大时,才考虑使用无锁编程。

小结

Go语言的并发编程中,选择合适的同步机制至关重要。互斥锁、条件变量和无锁编程各有优缺点,我们可以根据具体场景选择最合适的方式,以确保程序的安全性和性能。

results matching ""

    No results matching ""